package com.product.aspect;


import com.product.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

@Component
@Aspect
@Slf4j
public class AutoFillAspect {
       //自定义切点的位置，以及需要什么注解进行标识
       @Pointcut("execution(* com.product.mapper.*.*(..))&&@annotation(com.product.annotation.AutoFill)")
       public void pointcut() {

       }
       ///我们可以获取到关于当前连接点的详细信息，如方法名、参数、目标对象等。
       @Before("pointcut()")
       void before(JoinPoint joinPoint) {
            log.info("开启公共字段自动填充");
            //被代理的对象
            log.info("被代理的对象：{}",joinPoint.getTarget().getClass().getName());//实现了mapper接口的实例对象
            //代理对象自己
            log.info("代理对象自己:{}",joinPoint.getThis().getClass().getName());//jdk生成

            //1.获取到当前被拦截的方法上的数据库操作类型
            //方法的签名对象
            //joinPoint.getSignature();
            //在 Spring AOP 中，joinPoint.getSignature() 返回的是一个 Signature 接口实例。
            //这个接口是一个通用的接口，用于表示各种不同类型的签名（如方法签名、构造函数签名等）。
            //为了获取更具体的方法签名信息，你需要将这个通用的 Signature 接口实例转换为具体的实现类，比如 MethodSignature。
              MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
              AutoFill autoFill=methodSignature.getMethod().getAnnotation(AutoFill.class);
              OperationType operationType=autoFill.value();//通过注解声明值
            //2.获取到当前被拦截的方法接受的参数，拿到这个实体对象就是为了后续方便的赋值
            //joinPoint.getArgs() 返回的是实参（实际参数），获取实例,而不是形参（形式参数）。
              Object[] objects=joinPoint.getArgs();
              //不为空且长度大于0
              if(objects==null||objects.length==0){
                     return;
              }
              Object entity=objects[0];
              //准备赋值的数据
              LocalDateTime now=LocalDateTime.now();
              Long currentId= BaseContext.getCurrentId();
             //根据不同的操作类型通过反射为对应的属性赋值
            //插入操作
            if(operationType==OperationType.INSERT){
               //先获取方法对象
                try{
                    Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                    Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                    Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                    Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                    //通过反射来赋值，就是可以使用 invoke 方法在运行时调用这个 Method 对象所表示的方法
                    setCreateTime.invoke(entity, now);
                    setUpdateTime.invoke(entity, now);
                    setCreateUser.invoke(entity, currentId);
                    setUpdateUser.invoke(entity, currentId);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            //update操作
            else if(operationType==OperationType.UPDATE){
                try{
                    Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                    Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                    //通过反射来赋值，就是可以使用 invoke 方法在运行时调用这个 Method 对象所表示的方法
                    //当你将一个对象赋值给 Object 类型的引用时，编译器只能看到 Object 的方法。
                    // 因此，无法直接调用该对象特有的方法或属性，所以可以通过反射
                    setUpdateTime.invoke(entity, now);
                    setUpdateUser.invoke(entity, currentId);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }

       }

}
